package com.example.cacheredis.service.impl;

import com.example.cacheredis.mapper.DepartmentMapper;
import com.example.cacheredis.mapper.EmployeeMapper;
import com.example.cacheredis.pojo.Department;
import com.example.cacheredis.pojo.Employee;
import com.example.cacheredis.pojo.EmployeeExample;
import com.example.cacheredis.service.EmployeeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * @CacheConfig(cacheNames = "emp") 可以抽取缓存的公共配置，抽取了之后，这个类的所有
 */
//@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired(required = false)
    EmployeeMapper employeeMapper;

    @Autowired(required = false)
    DepartmentMapper departmentMapper;

    Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 需要开启缓存功能@EnableCaching
     * 将方法运行的结果进行缓存：以后再要相同的数据时，直接从缓存中获取，不用调用方法
     * CacheManager 管理多个Cache组件，对缓存的真正的CRUD操作再Cache组件中执行，每一个缓存组件有自己唯一的名字
     *
     * @return
     * @Cacheable 属性介绍
     * 1.cacheNames/value：指定缓存组件的名字
     * **cacheNames = "EmployeeCaches" 代表将查询到的结果放入名为EmployeeCaches的Cache缓存中
     * 2.key：缓存数据中使用的key，可以用它来指定。默认是使用方法中参数的名字
     * **key = "#id" 代表将方法参数中的userId作为这个缓存的key
     * key = "#id" 等效 key = "#root.args[0]"
     * 3.keyGenerator key生成器（默认用参数的值来作为key）
     * 4.key 和 keyGenerator 二选一
     * 5.cacheManager = "sdf" 指定缓存管理器
     * 6.cacheResolver = "" 指定缓存解析器
     * 7.缓存管理器和缓存解析器二选一
     * 7.condition = "" 指定符合条件的情况下才缓存
     * 8.unless="" 当unless中指定的条件为true，方法的返回值就不会被缓存，也可以获取到结果再进行判断
     * 9.sync 缓存是否使用异步模式
     * <p>
     * 一旦开启了缓存功能之后，缓存的自动配置类就会生效
     * org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
     * 会自动导入，这10个缓存类也需要满足类上的@Condition条件才会生效
     * org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration         Redis缓存
     * org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     * 可以在application.properties中添加debug=true可以查看哪一个缓存配置类生效
     * 默认是Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 生效
     * 他给容器中注入了一个ConcurrentMapCacheManager缓存管理器
     * 创建ConcurrentMap用于存放缓存
     *
     * </p>
     * unless = "#result == null" unless 条件满足的结果不会被缓存
     */
    @Cacheable(cacheNames = "empList", unless = "#result == null", key = "#root.methodName")
    @Override
    public List<Employee> findAllEmployees() {

        logger.info("进入数据库查询所有雇员信息");

        List<Employee> employees = this.employeeMapper.selectByExample(new EmployeeExample());

        for (Employee employee : employees) {
            Department department = this.departmentMapper.selectByPrimaryKey(employee.getDepartmentId());
            employee.setDepartment(department);
        }

        return employees;
    }

    /**
     * key = "#root.methodName+'['+#employeeId+']'" ----> 相当于在cache中根据 findEmpById[1] 来查找缓存，其中1 == employeeId
     * 不可以用 key = "#root.methodName" +"["+ "#employeeId" +"]" 这种形式，这样导致key随机
     * 但是不建议key这样写，方法名+id作为key没有什么意义，这里只是做个示范
     * <p>
     *
     * @param employeeId
     * @return
     * @Cacheable 注解的key不可以使用#result，因为@Cacheable在目标方法执行之前要根据key去查询缓存，而#result要方法执行完才能有结果
     * 逻辑bug
     * </p>
     */
    @Cacheable(cacheNames = "emp", key = "#employeeId")
    public Employee findEmpById(Integer employeeId) {
        logger.info("查询id为\t" + employeeId + "\t的用户");
        return this.employeeMapper.selectByPrimaryKey(employeeId);
    }

    /**
     * 可以根据 beanId从IOC容器中找出自定义的KeyGenerator
     * <p>
     * condition = "#a0>0" 从本方法（findEmpByDeptId）取出第0个参数，即 departmentId
     * 当departmentId > 0时，才对结果进行缓存
     * sync = true 使用同步模式，同步模式下 unless 条件就不可以使用
     *
     * @param departmentId
     * @return
     */
    @Cacheable(cacheNames = "emp-dept", key = "#departmentId", condition = "#a0>1", sync = true)
    @Override
    public List<Employee> findEmpByDeptId(Integer departmentId) {

        logger.info("在数据库中查询部门为" + departmentId + "的员工");

        EmployeeExample employeeExample = new EmployeeExample();
        employeeExample.createCriteria().andDepartmentIdEqualTo(departmentId);

        return this.employeeMapper.selectByExample(employeeExample);

    }

    /**
     * @param employee
     * @CachePut 方法一定执行，执行方法之后，根据方法返回的结果更新缓存（适用于更新操作，但是更新操作的方法必须有返回值）
     * <p>
     * 这里只做了，更新用户之后，同步更新findEmpById()中的缓存
     * </p>
     * 在这里key的这三种写法是等效的
     * key = "#employee.employeeId"
     * key = "#a0.employeeId"
     * key = "#result.employeeId"
     * 浏览器先访问 http://localhost:8080/getById/1
     * 再访问 http://localhost:8080/updateEmp?employeeId=1&address=随便
     * 浏览器再访问 http://localhost:8080/getById/1 发现缓存已经更新，但是没有查询数据库
     */
    @CachePut(cacheNames = "emp", key = "#a0.employeeId")
    @Override
    public Employee updateEmployee(Employee employee) {

        logger.info("更新" + employee);

        this.employeeMapper.updateByPrimaryKeySelective(employee);

        //return this.employeeMapper.selectByPrimaryKey(employee.getEmployeeId()); 没必要再去查一次数据库

        return employee;
    }


    /**
     * 从缓存中删除key
     * allEntries = true 删除 emp 缓存中的所有缓存
     * beforeInvocation = true 是否在方法执行之前，清除缓存，默认false：代表在方法执行之后再清除缓存
     * 在方法执行之后再清除缓存条件下，如果方法执行出异常，则方法执行之后不会清除缓存
     *
     * @param employeeId
     * @return
     */
    @CacheEvict(cacheNames = "emp", key = "#employeeId"/*,allEntries = true,beforeInvocation = true*/)
    @Override
    public Integer deleteEmpById(Integer employeeId) {
        logger.info("删除用户，id=" + employeeId);
        this.employeeMapper.deleteByPrimaryKey(employeeId);

        return employeeId;
    }

    /**
     * 如果缓存条件复杂，可以使用这个注解来组合其他缓存注解
     *
     * @param employeeName
     * @return
     */
    @Caching(
            cacheable = {
                    @Cacheable(cacheNames = "emp", key = "#employeeName")
            },
            put = { //目标方法一定执行
                    @CachePut(cacheNames = "emp",key = "#result.address",condition = "#result != null"),
                    @CachePut(cacheNames = "emp",key = "#result.departmentId",condition = "#result != null")
            }
    )
    @Override
    public Employee findEmployeeByName(String employeeName) {

        EmployeeExample employeeExample = new EmployeeExample();

        employeeExample.createCriteria().andEmployeeNameEqualTo(employeeName);

        List<Employee> employeeList = this.employeeMapper.selectByExample(employeeExample);

        if (!ObjectUtils.isEmpty(employeeList)) {
            return employeeList.get(0);
        }
        return null;
    }

    @Cacheable(cacheNames = "emp",key = "#address")
    @Override
    public Employee findEmployeeByAddress(String address) {
        return null;
    }
}
